﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using CardPlay.Model.CardModel;

namespace CardPlay.Model.GameModel
{
    public class CardPile : IEnumerable<Card>
    {
        private readonly List<Card> _components = new List<Card>();

        public event EventHandler<NotifyCardPileChangedEventArgs> CollectionChanged;

        public CardPile(string name = null)
        {
            Name = string.IsNullOrEmpty(name) ? String.Empty : name;
        }

        private CardPile(IEnumerable<Card> cards) : this(String.Empty)
        {
            _components.AddRange(cards);
        }

        public string Name
        {
            get; private set;
        }

        public bool IsEmpty
        {
            get { return Count == 0; }
        }

        public bool IsInSequence(CardFaceSequenceSpecification specification)
        {
            return Count >= 2 && Enumerable.Range(0, Count - 1).All(n => specification.IsSatisfiedBy(CardAt(n), CardAt(n + 1)));
        }

        public int StackLimit
        {
            get;
            set;
        }

        public int Count
        {
            get { return _components.Count; }
        }
        
        public PartLocation Top
        {
            get { return IsEmpty ? Location : new PartLocation(Name, TopIndex); }
        }

        public PartLocation Bottom
        {
            get { return IsEmpty ? Location : new PartLocation(Name, BottomIndex); }
        }

        public PartLocation Location
        {
            get { return new PartLocation(Name); }
        }
        
        public int TopIndex
        {
            get 
            { 
                Contract.Requires<InvalidOperationException>(!IsEmpty, "TopIndex called for empty pile.");
                return Count - 1;
            }
        }

        public int BottomIndex
        {
            get 
            { 
                Contract.Requires<InvalidOperationException>(!IsEmpty, "BottomIndex called for empty pile.");
                return 0;
            }
        }
    
        public Card TopCardOrNull
        {
            get { return IsEmpty ? null : CardAt(TopIndex); }
        }

        public Card TopCard
        {
            get { return CardAt(TopIndex); }
        }

        public Card BottomCard
        {
            get { return CardAt(BottomIndex); }
        }
    
        public int IndexOf(Card card)
        {
            return _components.IndexOf(card);
        }

        public PartLocation LocationOf(Card card)
        {
            return new PartLocation(Name, IndexOf(card));
        }

        public Card CardAt(int position)
        {
            Contract.Requires<ArgumentOutOfRangeException>(position >= 0 && position < Count, "position");
            return _components[position];
        }

        public bool Contains(Rank rank)
        {
            return _components.Any(card => card.Rank == rank);
        }
    
        public bool Contains(CardFace face)
        {
            Contract.Requires<ArgumentNullException>(face != null, "face");
            return _components.Any(card => card.Face == face);
        }

        public CardPile TopPile(int start)
        {
            return SubPile(start, Count);
        }

        public CardPile SubPile(int start, int end)
        {
            Contract.Requires<ArgumentOutOfRangeException>(start <= Count);
            return new CardPile(this.Skip(start).Take(Math.Min(end, Count) - start));
        }

        public void Reverse()
        {
            if (IsEmpty) return;

            var oldItems = this.ToList();
            _components.Reverse();

            RaiseCollectionChanged(NotifyCardPileChangedEventArgs.ForReplace(oldItems, _components));
        }
    
        public void FlipAllCards()
        {
            _components.ForEach(card => card.Flip());
        }
    
        public void Invert()
        {
            Reverse();
            FlipAllCards();
        }
    
        public bool Compact()
        {
            if (IsEmpty || this.Any(card => card == null)) return false;

            foreach (var index in Enumerable.Range(0, Count - 1).Where(i => _components[i] == null))
                RemoveCardAt(index);

            return true;
        }

        public void Sort(Comparison<Card> comparison)
        {
            _components.Sort(comparison);
        }

        public void AddCardToTop(Card card)
        {
            InsertCard(Count, card);
        }

        public void AddCardsToTop(IEnumerable<Card> cards)
        {
            InsertCards(Count, cards);
        }
    
        public void InsertCard(int index, Card card)
        {
            InsertCards(index, new[] {card});
        }    
    
        public void InsertCards(int index, IEnumerable<Card> cards)
        {
            _components.InsertRange(Math.Min(index, Count), cards);
            RaiseCollectionChanged(NotifyCardPileChangedEventArgs.ForAdd(cards.ToList(), index));
        }    
    
        public void RemoveTopCard()
        {
            RemoveCardAt(TopIndex);
        }

        public void RemoveCard(Card card)
        {
            Contract.Requires<ArgumentNullException>(card != null, "card");

            RemoveCardAt(_components.IndexOf(card));
        }

        public void RemoveCardAt(int index)
        {
            RemoveCards(index, 1);
        }

        public void RemoveCards(int index, int count)
        {
            Contract.Requires<ArgumentOutOfRangeException>(index >= 0 && index < Count, "index");
            Contract.Requires<ArgumentException>(index + count <= Count, "count");

            var oldItems = _components.Skip(index).Take(count).ToList();

            _components.RemoveRange(index, count);
            RaiseCollectionChanged(NotifyCardPileChangedEventArgs.ForRemove(oldItems, index));
        }
    
        public void RemoveAll()
        {
            _components.Clear();
            RaiseCollectionChanged(NotifyCardPileChangedEventArgs.ForReset());
        }

        public IEnumerator<Card> GetEnumerator()
        {
            return _components.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return _components.GetEnumerator();
        }

        private void RaiseCollectionChanged(NotifyCardPileChangedEventArgs args)
        {
            var notify = CollectionChanged;
            if (notify != null) notify(this, args);
        }
    }
}
